module NanoSeconds

export TimeDate, TimeOfDay, TimeSpan,
       Nanoseconds, Microseconds, Milliseconds, Seconds,
       Minutes, QuarterHours, Hours, Days, Weeks, Months, QuarterYears, Years,
       nanoseconds, microseconds, milliseconds, seconds,
       minutes, quarterhours, hours, days, weeks, months, quarteryears, years,
       Nanosecond, Microsecond, Millisecond, Second,
       Minute, QuarterHour, Hour, Day, Week, Month, QuarterYear, Year,
       nanosecond, microsecond, millisecond, second,
       minute, quarterhour, hour, day, week, month, quarteryear, year


const Int4Time = Int64
const Int4Span = Int4Time

const NANOSECONDS_PER_NANOSECOND    = one(Int4Span)
const NANOSECONDS_PER_MICROSECOND   = Int4Span(one(Int4Span) * 1_000)
const NANOSECONDS_PER_MILLISECOND   = Int4Span(one(Int4Span) * 1_000_000)
const NANOSECONDS_PER_SECOND        = Int4Span(one(Int4Span) * 1_000_000_000)
const NANOSECONDS_PER_MINUTE        = Int4Span(one(Int4Span) * 1_000_000_000 * 60)
const NANOSECONDS_PER_QUARTERHOUR   = Int4Span(one(Int4Span) * 1_000_000_000 * 60 * 15)
const NANOSECONDS_PER_HOUR          = Int4Span(one(Int4Span) * 1_000_000_000 * 60 * 60)
const NANOSECONDS_PER_DAY           = Int4Span(one(Int4Span) * 1_000_000_000 * 60 * 60 * 24)
const NANOSECONDS_PER_WEEK          = Int4Span(one(Int4Span) * 1_000_000_000 * 60 * 60 * 24 * 7)

const MICROSECONDS_PER_NANOSECOND   = one(Int4Span)//1_000
const MICROSECONDS_PER_MICROSECOND  = one(Int4Span)
const MICROSECONDS_PER_MILLISECOND  = Int4Span(one(Int4Span) * 1_000)
const MICROSECONDS_PER_SECOND       = Int4Span(one(Int4Span) * 1_000_000)
const MICROSECONDS_PER_MINUTE       = Int4Span(one(Int4Span) * 1_000_000 * 60)
const MICROSECONDS_PER_QUARTERHOUR  = Int4Span(one(Int4Span) * 1_000_000 * 60 * 15)
const MICROSECONDS_PER_HOUR         = Int4Span(one(Int4Span) * 1_000_000 * 60 * 60)
const MICROSECONDS_PER_DAY          = Int4Span(one(Int4Span) * 1_000_000 * 60 * 60 * 24)
const MICROSECONDS_PER_WEEK         = Int4Span(one(Int4Span) * 1_000_000 * 60 * 60 * 24 * 7)

const MILLISECONDS_PER_NANOSECOND   = one(Int4Span)//1_000_000
const MILLISECONDS_PER_MICROSECOND  = one(Int4Span)//1_000
const MILLISECONDS_PER_MILLISECOND  = one(Int4Span)
const MILLISECONDS_PER_SECOND       = Int4Span(one(Int4Span) * 1_000)
const MILLISECONDS_PER_MINUTE       = Int4Span(one(Int4Span) * 1_000 * 60)
const MILLISECONDS_PER_QUARTERHOUR  = Int4Span(one(Int4Span) * 1_000 * 60 * 15)
const MILLISECONDS_PER_HOUR         = Int4Span(one(Int4Span) * 1_000 * 60 * 60)
const MILLISECONDS_PER_DAY          = Int4Span(one(Int4Span) * 1_000 * 60 * 60 * 24)
const MILLISECONDS_PER_WEEK         = Int4Span(one(Int4Span) * 1_000 * 60 * 60 * 24 * 7)

const SECONDS_PER_NANOSECOND        = one(Int4Span)//1_000_000_000
const SECONDS_PER_MICROSECOND       = one(Int4Span)//1_000_000
const SECONDS_PER_MILLISECOND       = one(Int4Span)//1_000
const SECONDS_PER_SECOND            = one(Int4Span)
const SECONDS_PER_MINUTE            = Int4Span(60)
const SECONDS_PER_QUARTERHOUR       = Int4Span(60 * 15)
const SECONDS_PER_HOUR              = Int4Span(one(Int4Span) * 60 * 60)
const SECONDS_PER_DAY               = Int4Span(one(Int4Span) * 60 * 60 * 24)
const SECONDS_PER_WEEK              = Int4Span(one(Int4Span) * 60 * 60 * 24 * 7)

const MINUTES_PER_NANOSECOND        = one(Int4Span)//(1_000_000_000 * 60)
const MINUTES_PER_MICROSECOND       = one(Int4Span)//(1_000_000 * 60)
const MINUTES_PER_MILLISECOND       = one(Int4Span)//(1_000 * 60)
const MINUTES_PER_SECOND            = one(Int4Span)//60
const MINUTES_PER_MINUTE            = one(Int4Span)
const MINUTES_PER_QUARTERHOUR       = Int4Span(15)
const MINUTES_PER_HOUR              = Int4Span(60)
const MINUTES_PER_DAY               = Int4Span(60 * 24)
const MINUTES_PER_WEEK              = Int4Span(60 * 24 * 7)

const QUARTERHOURS_PER_NANOSECOND   = one(Int4Span)//(1_000_000_000 * 60 * 15)
const QUARTERHOURS_PER_MICROSECOND  = one(Int4Span)//(1_000_000 * 60 * 15)
const QUARTERHOURS_PER_MILLISECOND  = one(Int4Span)//(1_000 * 60 * 15)
const QUARTERHOURS_PER_SECOND       = one(Int4Span)//(60 * 15)
const QUARTERHOURS_PER_MINUTE       = one(Int4Span)//15
const QUARTERHOURS_PER_QUARTERHOUR  = one(Int4Span)
const QUARTERHOURS_PER_HOUR         = Int4Span(4)
const QUARTERHOURS_PER_DAY          = Int4Span(4 * 24)
const QUARTERHOURS_PER_WEEK         = Int4Span(4 * 24 * 7)

const HOURS_PER_NANOSECOND          = one(Int4Span)//(1_000_000_000 * 60 * 60)
const HOURS_PER_MICROSECOND         = one(Int4Span)//(1_000_000 * 60 * 60)
const HOURS_PER_MILLISECOND         = one(Int4Span)//(1_000 * 60 * 60)
const HOURS_PER_SECOND              = one(Int4Span)//(60 * 60)
const HOURS_PER_MINUTE              = one(Int4Span)//60
const HOURS_PER_QUARTERHOUR         = one(Int4Span)//Int4Span(4)
const HOURS_PER_HOUR                = one(Int4Span)
const HOURS_PER_DAY                 = Int4Span(one(Int4Span) * 24)
const HOURS_PER_WEEK                = Int4Span(one(Int4Span) * 24 * 7)

const DAYS_PER_NANOSECOND           = one(Int4Span)//(1_000_000_000 * 60 * 60 * 24)
const DAYS_PER_MICROSECOND          = one(Int4Span)//(1_000_000 * 60 * 60 * 24)
const DAYS_PER_MILLISECOND          = one(Int4Span)//(1_000 * 60 * 60 * 24)
const DAYS_PER_SECOND               = one(Int4Span)//(60 * 60 * 24)
const DAYS_PER_MINUTE               = one(Int4Span)//(60 * 24)
const DAYS_PER_QUARTERHOUR          = one(Int4Span)//(15 * 24)
const DAYS_PER_HOUR                 = one(Int4Span)//24
const DAYS_PER_DAY                  = one(Int4Span)
const DAYS_PER_WEEK                 = Int4Span(one(Int4Span) * 7)

const WEEKS_PER_NANOSECOND          = one(Int4Span)//(1_000_000_000 * 60 * 60 * 24 * 7)
const WEEKS_PER_MICROSECOND         = one(Int4Span)//(1_000_000 * 60 * 60 * 24 * 7)
const WEEKS_PER_MILLISECOND         = one(Int4Span)//(1_000 * 60 * 60 * 24 * 7)
const WEEKS_PER_SECOND              = one(Int4Span)//(60 * 60 * 24 * 7)
const WEEKS_PER_MINUTE              = one(Int4Span)//(60 * 24 * 7)
const WEEKS_PER_QUARTERHOUR         = one(Int4Span)//(15 * 24 * 7)
const WEEKS_PER_HOUR                = one(Int4Span)//(24 * 7)
const WEEKS_PER_DAY                 = one(Int4Span)//7
const WEEKS_PER_WEEK                = one(Int4Span)

const MONTHS_PER_QUARTERYEAR        = Int4Span(3)
const MONTHS_PER_HALFYEAR           = Int4Span(6)
const MONTHS_PER_YEAR               = Int4Span(12)
const MONTHS_PER_DECADE             = Int4Span(12*10)
const QUARTERYEARS_PER_HALFYEAR     = Int4Span(2)
const QUARTERYEARS_PER_YEAR         = Int4Span(4)
const QUARTERYEARS_PER_DECADE       = Int4Span(4*10)
const HALFYEARS_PER_YEAR            = Int4Span(2)
const HALFYEARS_PER_DECADE          = Int4Span(2*10)
const YEARS_PER_DECADE              = Int4Span(10)





abstract type Temporal end
abstract type Duration <: Temporal end
abstract type Granular <: Duration end

abstract type NanosecGrain     <: Granular          end
abstract type MicrosecGrain    <: NanosecGrain      end
abstract type MillisecGrain    <: MicrosecGrain     end
abstract type SecondGrain      <: MillisecGrain     end
abstract type MinuteGrain      <: SecondGrain       end
abstract type QuarterHourGrain <: MinuteGrain       end
abstract type HourGrain        <: QuarterHourGrain  end
abstract type DayGrain         <: HourGrain         end
abstract type WeekGrain        <: DayGrain          end

abstract type MonthGrain       <: Granular         end
abstract type QuarterYearGrain <: MonthGrain       end
abstract type YearGrain        <: QuarterYearGrain end

struct nanoseconds  <: Signed end
struct microseconds <: Signed end
struct milliseconds <: Signed end
struct seconds      <: Signed end
struct minutes      <: Signed end
struct quarterhours <: Signed end
struct hours        <: Signed end
struct days         <: Signed end
struct weeks        <: Signed end
struct months       <: Signed end
struct quarteryears <: Signed end
struct years        <: Signed end

for (T,G) in ((:Nanoseconds, :NanosecGrain), (:Microseconds, :MicrosecGrain),
              (:Milliseconds, :MillisecGrain), (:Seconds, :SecondGrain),
              (:Minutes, :MinuteGrain), (:QuarterHours, :QuarterHourGrain),
              (:Hours, :HourGrain), (:Days, :DayGrain), (:Weeks, :WeekGrain),
              (:Months, :MonthGrain), (:QuarterYears, :QuarterYearGrain), (:Years, :YearGrain))
  @eval begin
    struct $T <: $G
        value::Int4Span
    end
    @inline value(x::$T) = x.value
  end
end

for (T,S) in ((:Nanoseconds, :nanoseconds), (:Microseconds, :microseconds),
              (:Milliseconds, :milliseconds), (:Seconds, :seconds),
              (:Minutes, :minutes), (:QuarterHours, :quarterhours),
              (:Hours, :hours), (:Days, :days), (:Weeks, :weeks), 
              (:Months, :months), (:QuarterYears, :quarteryears), (:Years, :years))
  @eval begin
    Base.:(*)(x::Int4Span, ::Type{$S})  = $T(x)
    Base.:(*)(x::I, ::Type{$S})  where I<:Signed = $T(convert(Int4Span,x))
  end
end

for (F, G) in ((:nanoseconds, :Nanoseconds),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P) in ((:Microseconds, :NANOSECONDS_PER_MICROSECOND), (:Milliseconds, :NANOSECONDS_PER_MILLISECOND),
                 (:Seconds, :NANOSECONDS_PER_SECOND), (:Minutes, :NANOSECONDS_PER_MINUTE),
                 (:QuarterHours, :NANOSECONDS_PER_QUARTERHOUR), (:Hours, :NANOSECONDS_PER_HOUR),
                 (:Days, :NANOSECONDS_PER_DAY), (:Weeks, :NANOSECONDS_PER_WEEK))
     @eval begin
        $F(x::$S) = value(x) * $P
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:microseconds, :Microseconds),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Nanoseconds, :NANOSECONDS_PER_MICROSECOND, true), 
                    (:Milliseconds, :MICROSECONDS_PER_MILLISECOND, false), (:Seconds, :MICROSECONDS_PER_SECOND, false),
                    (:Minutes, :MICROSECONDS_PER_MINUTE, false), (:QuarterHours, :MICROSECONDS_PER_QUARtERHOUR, false), 
                    (:Hours, :MICROSECONDS_PER_HOUR, false),
                    (:Days, :MICROSECONDS_PER_DAY, false), (:Weeks, :MICROSECONDS_PER_WEEK, false))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:milliseconds, :Milliseconds),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Nanoseconds, :NANOSECONDS_PER_MLLISECOND, true), (:Microseconds, :MICROSECONDS_PER_MILLISECOND, true),
                    (:Seconds, :MILLISECONDS_PER_SECOND, false), (:Minutes, :MILLISECONDS_PER_MINUTE, false),
                    (:QuarterHours, :MILLISECONDS_PER_QUARTERHOUR, false), 
                    (:Hours, :MILLISECONDS_PER_HOUR, false), (:Days, :MILLISECONDS_PER_DAY, false), 
                    (:Weeks, :MILLISECONDS_PER_WEEK, false))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end                            
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:seconds, :Seconds),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Nanoseconds, :NANOSECONDS_PER_SECOND, true), (:Microseconds, :MICROSECONDS_PER_SECOND, true),
                    (:Milliseconds, :MILLISECONDS_PER_SECOND, true),
                    (:Minutes, :SECONDS_PER_MINUTE, false), (:QuarterHours, :SECONDS_PER_QUARTERHOUR, false),
                    (:Hours, :SECONDS_PER_HOUR, false),
                    (:Days, :SECONDS_PER_DAY, false), (:Weeks, :SECONDS_PER_WEEK, false))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end 
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:minutes, :Minutes),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Nanoseconds, :NANOSECONDS_PER_MINUTE, true), (:Microseconds, :MICROSECONDS_PER_MINUTE, true),
                    (:Milliseconds, :MILLISECONDS_PER_MINUTE, true), (:Seconds, :SECONDS_PER_MINUTE, true),                    (:Hours, :SECONDS_PER_HOUR, false),
                    (:QuarterHours, :MINUTES_PER_QUARTERHOUR, false),
                    (:Hours, :MINUTES_PER_HOUR, false), (:Days, :MINUTES_PER_DAY, false),
                    (:Weeks, :MINUTES_PER_WEEK, false))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end


for (F, G) in ((:quarterhours, :QuarterHours),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Nanoseconds, :NANOSECONDS_PER_HOUR, true), (:Microseconds, :MICROSECONDS_PER_HOUR, true),
                    (:Milliseconds, :MILLISECONDS_PER_HOUR, true), (:Seconds, :SECONDS_PER_HOUR, true),
                    (:Minutes, :MINUTES_PER_HOUR, true), (:Hours, :QUARTERHOURS_PER_HOUR, false),
                    (:Days, :MINUTES_PER_DAY, false), (:Weeks, :MINUTES_PER_WEEK, false))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:hours, :Hours),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Nanoseconds, :NANOSECONDS_PER_HOUR, true), (:Microseconds, :MICROSECONDS_PER_HOUR, true),
                    (:Milliseconds, :MILLISECONDS_PER_HOUR, true), (:Seconds, :SECONDS_PER_HOUR, true),
                    (:Minutes, :MINUTES_PER_HOUR, true), (:QuarterHours, :QUARTERHOURS_PER_HOUR, true),
                    (:Days, :MINUTES_PER_DAY, false), (:Weeks, :MINUTES_PER_WEEK, false))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:days, :Days),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Nanoseconds, :NANOSECONDS_PER_DAY, true), (:Microseconds, :MICROSECONDS_PER_DAY, true),
                    (:Milliseconds, :MILLISECONDS_PER_DAY, true), (:Seconds, :SECONDS_PER_DAY, true),
                    (:Minutes, :MINUTES_PER_DAY, true), (:QuarterHours, :QUARTERHOURS_PER_DAY, true), 
                    (:Hours, :MINUTES_PER_DAY, true), (:Weeks, :DAYS_PER_WEEK, false))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:weeks, :Weeks),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Nanoseconds, :NANOSECONDS_PER_WEEK, true), (:Microseconds, :MICROSECONDS_PER_WEEK, true),
                    (:Milliseconds, :MILLISECONDS_PER_WEEK, true), (:Seconds, :SECONDS_PER_WEEK, true),
                    (:Minutes, :MINUTES_PER_WEEK, true), (:QuarterHours, :QUARTERHOURS_PER_WEEK, true),
                    (:Hours, :MINUTES_PER_WEEK, true), (:Days, :DAYS_PER_WEEK, true))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end


for (F, G) in ((:months, :Months),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ( (:QuarterYears, :MONTHS_PER_QUARTERYEAR, false),
                     (:Years, :MONTHS_PER_YEAR, false),)
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:quarteryears, :QuarterYears),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Months, :MONTHS_PER_QUARTERYEAR, true),
                    (:Years, :QUARTERYEARS_PER_YEAR, false))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end

for (F, G) in ((:years, :Years),)
  @eval begin
    $F(x::$G) = value(x)
    $G(x::$G) = x
  end
  for (S, P, B) in ((:Months, :MONTHS_PER_YEAR, true),
                    (:QuarterYears, :QUARTERYEARS_PER_YEAR, true))
     @eval begin
        if $B
            $F(x::$S) = div(value(x), $P)
        else                           
            $F(x::$S) = value(x) * $P
        end
        $G(x::$S) = $G($F(x))
     end
  end
end








const PER = [ 
    [NANOSECONDS_PER_NANOSECOND, NANOSECONDS_PER_MICROSECOND, NANOSECONDS_PER_MILLISECOND, NANOSECONDS_PER_SECOND, NANOSECONDS_PER_MINUTE, NANOSECONDS_PER_HOUR, NANOSECONDS_PER_DAY, NANOSECONDS_PER_WEEK],
    [MICROSECONDS_PER_NANOSECOND, MICROSECONDS_PER_MICROSECOND, MICROSECONDS_PER_MILLISECOND, MICROSECONDS_PER_SECOND, MICROSECONDS_PER_MINUTE, MICROSECONDS_PER_HOUR, MICROSECONDS_PER_DAY, MICROSECONDS_PER_WEEK],
    [MILLISECONDS_PER_NANOSECOND, MILLISECONDS_PER_MICROSECOND, MILLISECONDS_PER_MILLISECOND, MILLISECONDS_PER_SECOND, MILLISECONDS_PER_MINUTE, MILLISECONDS_PER_HOUR, MILLISECONDS_PER_DAY, MILLISECONDS_PER_WEEK],
    [SECONDS_PER_NANOSECOND, SECONDS_PER_MICROSECOND, SECONDS_PER_MILLISECOND, SECONDS_PER_SECOND, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK],
    [MINUTES_PER_NANOSECOND, MINUTES_PER_MICROSECOND, MINUTES_PER_MILLISECOND, MINUTES_PER_SECOND, MINUTES_PER_MINUTE, MINUTES_PER_HOUR, MINUTES_PER_DAY, MINUTES_PER_WEEK],
    [HOURS_PER_NANOSECOND, HOURS_PER_MICROSECOND, HOURS_PER_MILLISECOND, HOURS_PER_SECOND, HOURS_PER_MINUTE, HOURS_PER_HOUR, HOURS_PER_DAY, HOURS_PER_WEEK],
    [DAYS_PER_NANOSECOND, DAYS_PER_MICROSECOND, DAYS_PER_MILLISECOND, DAYS_PER_SECOND, DAYS_PER_MINUTE, DAYS_PER_HOUR, DAYS_PER_DAY, DAYS_PER_WEEK],
    [WEEKS_PER_NANOSECOND, WEEKS_PER_MICROSECOND, WEEKS_PER_MILLISECOND, WEEKS_PER_SECOND, WEEKS_PER_MINUTE, WEEKS_PER_HOUR, WEEKS_PER_DAY, WEEKS_PER_WEEK],
  ]

for (IDX, T) in enumerate(:Nanoseconds, :Microseconds, :Seconds, :Minutes, :Hours, :Days, :Weeks)
    @eval nanoseconds(x::$T) = value(x) * PER[1][$IDX]
end
for (IDX, T) in enumerate(:Nanoseconds, :Microseconds, :Seconds, :Minutes, :Hours, :Days, :Weeks)
    @eval begin
        k = PER[2][$IDX]
        if k < 1
           m = k.den          
           microseconds(x::$T) = div(value(x), m)
        else
           m = k
           microseconds(x::$T) = value(x) * m
        end             
    end
end

nanoseconds(x::Nanoseconds)   = value(x)
nanoseconds(x::Microseconds)  = value(x) * NANOSECONDS_PER_MICROSECOND
nanoseconds(x::Milliseconds)  = value(x) * NANOSECONDS_PER_MILLISECOND
nanoseconds(x::Seconds)       = value(x) * NANOSECONDS_PER_SECOND
nanoseconds(x::Minutes)       = value(x) * NANOSECONDS_PER_MINUTE
inanoseconds(x::Hours)         = value(x) * NANOSECONDS_PER_HOUR
nanoseconds(x::Days)          = value(x) * NANOSECONDS_PER_DAY
nanoseconds(x::Weeks)         = value(x) * NANOSECONDS_PER_WEEK

microseconds(x::Nanoseconds)  = div(value(x), NANOSECONDS_PER_MICROSECOND)
microseconds(x::Microseconds) = value(x)
microseconds(x::Milliseconds) = value(x) * MICROSECONDS_PER_MILLISECOND
microseconds(x::Seconds)      = value(x) * MICROSECONDS_PER_SECOND
microseconds(x::Minutes)      = value(x) * MICROSECONDS_PER_MINUTE
microseconds(x::Hours)        = value(x) * MICROSECONDS_PER_HOUR
microseconds(x::Days)         = value(x) * MICROSECONDS_PER_DAY
microseconds(x::Weeks)        = value(x) * MICROSECONDS_PER_WEEK

milliseconds(x::Nanoseconds)  = div(value(x), NANOSECONDS_PER_MILLISECOND)
milliseconds(x::Microseconds) = div(value(x), MICROSECONDS_PER_MILLISECOND)
milliseconds(x::Milliseconds) = value(x)
milliseconds(x::Seconds)      = value(x) * MILLISECONDS_PER_SECOND
milliseconds(x::Minutes)      = value(x) * MILLISECONDS_PER_MINUTE
milliseconds(x::Hours)        = value(x) * MILLISECONDS_PER_HOUR
milliseconds(x::Days)         = value(x) * MILLISECONDS_PER_DAY
milliseconds(x::Weeks)        = value(x) * MILLISECONDS_PER_WEEK

seconds(x::Nanoseconds)       = div(value(x), NANOSECONDS_PER_SECOND)
seconds(x::Microseconds)      = div(value(x), MICROSECONDS_PER_SECOND)
seconds(x::Milliseconds)      = div(value(x), MILLISECONDS_PER_SECOND)
seconds(x::Seconds)           = value(x)
seconds(x::Minutes)           = value(x) * SECONDS_PER_MINUTE
seconds(x::Hours)             = value(x) * SECONDS_PER_HOUR
seconds(x::Days)              = value(x) * SECONDS_PER_DAY
seconds(x::Weeks)             = value(x) * SECONDS_PER_WEEK

minutes(x::Nanoseconds)       = div(value(x), NANOSECONDS_PER_MINUTE)
minutes(x::Microseconds)      = div(value(x), MICROSECONDS_PER_MINUTE)
minutes(x::Milliseconds)      = div(value(x), MILLISECONDS_PER_MINUTE)
minutes(x::Seconds)           = div(value(x), SECONDS_PER_MINUTE)
minutes(x::Minutes)           = value(x)
minutes(x::Hours)             = value(x) * MINUTES_PER_HOUR
minutes(x::Days)              = value(x) * MINUTES_PER_DAY
minutes(x::Weeks)             = value(x) * MINUTES_PER_WEEK

hours(x::Nanoseconds)         = div(value(x), NANOSECONDS_PER_HOUR)
hours(x::Microseconds)        = div(value(x), MICROSECONDS_PER_HOUR)
hours(x::Milliseconds)        = div(value(x), MILLISECONDS_PER_HOUR)
hours(x::Seconds)             = div(value(x), SECONDS_PER_HOUR)
hours(x::Minutes)             = div(value(x), MINUTES_PER_HOUR)
hours(x::Hours)               = value(x)
hours(x::Days)                = value(x) * MINUTES_PER_DAY
hours(x::Weeks)               = value(x) * MINUTES_PER_WEEK

days(x::Nanoseconds)         = div(value(x), NANOSECONDS_PER_DAY)
days(x::Microseconds)        = div(value(x), MICROSECONDS_PER_DAY)
days(x::Milliseconds)        = div(value(x), MILLISECONDS_PER_DAY)
days(x::Seconds)             = div(value(x), SECONDS_PER_DAY)
days(x::Minutes)             = div(value(x), MINUTES_PER_DAY)
days(x::Hours)               = div(value(x), HOURS_PER_DAY)
days(x::Days)                = value(x)
days(x::Weeks)               = value(x) * DAYS_PER_WEEK

weeks(x::Nanoseconds)        = div(value(x), NANOSECONDS_PER_WEEK)
weeks(x::Microseconds)       = div(value(x), MICROSECONDS_PER_WEEK)
weeks(x::Milliseconds)       = div(value(x), MILLISECONDS_PER_WEEK)
weeks(x::Seconds)            = div(value(x), SECONDS_PER_WEEK)
weeks(x::Minutes)            = div(value(x), MINUTES_PER_WEEK)
weeks(x::Hours)              = div(value(x), HOURS_PER_WEEK)
weeks(x::Days)               = div(value(x), DAYS_PER_WEEK)
weeks(x::Weeks)              = value(x)



for T in (:Nanoseconds, :Microseconds, :Seconds, :Minutes, :Hours, :Days, :Weeks)
    @eval begin
        Base.convert(::Type{Nanoseconds},  x::$T)  = Nanoseconds(nanoseconds(x))
        Base.convert(::Type{Microseconds}, x::$T) = Microseconds(microseconds(x))
        Base.convert(::Type{Milliseconds}, x::$T) = Milliseconds(milliseconds(x))
        Base.convert(::Type{Seconds}, x::$T) = Seconds(seconds(x))
        Base.convert(::Type{Minutes}, x::$T) = Minutes(minutes(x))
        Base.convert(::Type{Hours}, x::$T)   = Hours(hours(x))
        Base.convert(::Type{Days}, x::$T)    = Days(days(x))
        Base.convert(::Type{Weeks}, x::$T)   = Weeks(weeks(x))
          
        Nanoseconds(x::$T)  = Nanoseconds(nanoseconds(x))
        Microseconds(x::$T) = Microseconds(microseconds(x))
        Milliseconds(x::$T) = Milliseconds(milliseconds(x))
        Seconds(x::$T) = Seconds(seconds(x))
        Minutes(x::$T) = Minutes(minutes(x))
        Hours(x::$T)   = Hours(hours(x))
        Days(x::$T)    = Days(days(x))
        Weeks(x::$T)   = Weeks(weeks(x))
    end
end



Base.convert(::Type{Int4Span}, x::Nanoseconds)  = value(x)
Base.convert(::Type{Int4Span}, x::Microseconds) = value(x)
Base.convert(::Type{Int4Span}, x::Milliseconds) = value(x)
Base.convert(::Type{Int4Span}, x::Seconds)      = value(x)
Base.convert(::Type{Int4Span}, x::Minutes)      = value(x)
Base.convert(::Type{Int4Span}, x::Hours)        = value(x)
Base.convert(::Type{Int4Span}, x::Days)         = value(x)
Base.convert(::Type{Int4Span}, x::Weeks)        = value(x)

Base.convert(::Type{Nanoseconds},  x::Int4Span) = Nanoseconds(x)
Base.convert(::Type{Microseconds}, x::Int4Span) = Microseconds(x)
Base.convert(::Type{Milliseconds}, x::Int4Span) = Milliseconds(x)
Base.convert(::Type{Seconds},      x::Int4Span) = Seconds(x)
Base.convert(::Type{Minutes},      x::Int4Span) = Minutes(x)
Base.convert(::Type{Hours},        x::Int4Span) = Hours(x)
Base.convert(::Type{Days},         x::Int4Span) = Days(x)
Base.convert(::Type{Weeks},        x::Int4Span) = Weeks(x)


Base.convert(::Type{Nanoseconds}, x::Nanoseconds)   = x
Base.convert(::Type{Nanoseconds}, x::Microseconds)  = Nanoseconds( nanoseconds(x) )
Base.convert(::Type{Nanoseconds}, x::Milliseconds)  = Nanoseconds( nanoseconds(x) )
Base.convert(::Type{Nanoseconds}, x::Seconds)       = Nanoseconds( nanoseconds(x) )
Base.convert(::Type{Nanoseconds}, x::Minutes)       = Nanoseconds( nanoseconds(x) )
Base.convert(::Type{Nanoseconds}, x::Hours)         = Nanoseconds( nanoseconds(x) )
Base.convert(::Type{Nanoseconds}, x::Days)          = Nanoseconds( nanoseconds(x) )
Base.convert(::Type{Nanoseconds}, x::Weeks)         = Nanoseconds( nanoseconds(x) )

Base.convert(::Type{Microseconds}, x::Nanoseconds)  = Microseconds( microseconds(x) )
Base.convert(::Type{Microseconds}, x::Microseconds) = x
Base.convert(::Type{Microseconds}, x::Milliseconds) = Microseconds( microseconds(x) )
Base.convert(::Type{Microseconds}, x::Seconds)      = Microseconds( microseconds(x) )
Base.convert(::Type{Microseconds}, x::Minutes)      = Microseconds( microseconds(x) )
Base.convert(::Type{Microseconds}, x::Hours)        = Microseconds( microseconds(x) )
Base.convert(::Type{Microseconds}, x::Days)         = Microseconds( microseconds(x) )
Base.convert(::Type{Microseconds}, x::Weeks)        = Microseconds( microseconds(x) )

Base.convert(::Type{Milliseconds}, x::Nanoseconds)  = Milliseconds( milliseconds(x) )
Base.convert(::Type{Milliseconds}, x::Microseconds) = Milliseconds( milliseconds(x) )
Base.convert(::Type{Milliseconds}, x::Milliseconds) = x
Base.convert(::Type{Milliseconds}, x::Seconds)      = Milliseconds( milliseconds(x) )
Base.convert(::Type{Milliseconds}, x::Minutes)      = Milliseconds( milliseconds(x) )
Base.convert(::Type{Milliseconds}, x::Hours)        = Milliseconds( milliseconds(x) )
Base.convert(::Type{Milliseconds}, x::Days)         = Milliseconds( milliseconds(x) )
Base.convert(::Type{Milliseconds}, x::Weeks)        = Milliseconds( milliseconds(x) )

Base.convert(::Type{Seconds}, x::Nanoseconds)  = Seconds( seconds(x) )
Base.convert(::Type{Seconds}, x::Microseconds) = Seconds( seconds(x) )
Base.convert(::Type{Seconds}, x::Milliseconds) = Seconds( seconds(x) )
Base.convert(::Type{Seconds}, x::Seconds)      = x
Base.convert(::Type{Seconds}, x::Minutes)      = Seconds( seconds(x) )
Base.convert(::Type{Seconds}, x::Hours)        = Seconds( seconds(x) )
Base.convert(::Type{Seconds}, x::Days)         = Seconds( seconds(x) )
Base.convert(::Type{Seconds}, x::Weeks)        = Seconds( seconds(x) )

Base.convert(::Type{Minutes}, x::Nanoseconds)  = Minutes( minutes(x) )
Base.convert(::Type{Minutes}, x::Microseconds) = Minutes( minutes(x) )
Base.convert(::Type{Minutes}, x::Milliseconds) = Minutes( minutes(x) )
Base.convert(::Type{Minutes}, x::Seconds)      = Minutes( minutes(x) )
Base.convert(::Type{Minutes}, x::Minutes)      = x
Base.convert(::Type{Minutes}, x::Hours)        = Minutes( minutes(x) )
Base.convert(::Type{Minutes}, x::Days)         = Minutes( minutes(x) )
Base.convert(::Type{Minutes}, x::Weeks)        = Minutes( minutes(x) )

Base.convert(::Type{Hours}, x::Nanoseconds)    = Hours( hours(x) )
Base.convert(::Type{Hours}, x::Microseconds)   = Hours( hours(x) )
Base.convert(::Type{Hours}, x::Milliseconds)   = Hours( hours(x) )
Base.convert(::Type{Hours}, x::Seconds)        = Hours( hours(x) )
Base.convert(::Type{Hours}, x::Minutes)        = Hours( hours(x) )
Base.convert(::Type{Hours}, x::Hours)          = x
Base.convert(::Type{Hours}, x::Days)           = Hours( hours(x) )
Base.convert(::Type{Hours}, x::Weeks)          = Hours( hours(x) )

Base.convert(::Type{Days}, x::Nanoseconds)    = Days( days(x) )
Base.convert(::Type{Days}, x::Microseconds)   = Days( days(x) )
Base.convert(::Type{Days}, x::Milliseconds)   = Days( days(x) )
Base.convert(::Type{Days}, x::Seconds)        = Days( days(x) )
Base.convert(::Type{Days}, x::Minutes)        = Days( days(x) )
Base.convert(::Type{Days}, x::Hours)          = x
Base.convert(::Type{Days}, x::Days)           = Days( days(x) )
Base.convert(::Type{Days}, x::Weeks)          = Days( days(x) )

Base.convert(::Type{Weeks}, x::Nanoseconds)    = Weeks( weeks(x) )
Base.convert(::Type{Weeks}, x::Microseconds)   = Weeks( weeks(x) )
Base.convert(::Type{Weeks}, x::Milliseconds)   = Weeks( weeks(x) )
Base.convert(::Type{Weeks}, x::Seconds)        = Weeks( weeks(x) )
Base.convert(::Type{Weeks}, x::Minutes)        = Weeks( weeks(x) )
Base.convert(::Type{Weeks}, x::Hours)          = x
Base.convert(::Type{Weeks}, x::Days)           = Weeks( weeks(x) )
Base.convert(::Type{Weeks}, x::Weeks)          = Weeks( weeks(x) )




Nanoseconds(x::Nanoseconds)   = x
Nanoseconds(x::Microseconds)  = Nanoseconds( nanoseconds(x) )
Nanoseconds(x::Milliseconds)  = Nanoseconds( nanoseconds(x) )
Nanoseconds(x::Seconds)       = Nanoseconds( nanoseconds(x) )
Nanoseconds(x::Minutes)       = Nanoseconds( nanoseconds(x) )
Nanoseconds(x::Hours)         = Nanoseconds( nanoseconds(x) )
Nanoseconds(x::Days)          = Nanoseconds( nanoseconds(x) )
Nanoseconds(x::Weeks)         = Nanoseconds( nanoseconds(x) )

Microseconds(x::Nanoseconds)   = Microseconds( microseconds(x) )
Microseconds(x::Microseconds)  = x
Microseconds(x::Milliseconds)  = Microseconds( microseconds(x) )
Microseconds(x::Seconds)       = Microseconds( microseconds(x) )
Microseconds(x::Minutes)       = Microseconds( microseconds(x) )
Microseconds(x::Hours)         = Microseconds( microseconds(x) )
Microseconds(x::Days)          = Microseconds( microseconds(x) )
Microseconds(x::Weeks)         = Microseconds( microseconds(x) )

Milliseconds(x::Nanoseconds)   = Milliseconds( milliseconds(x) )
Milliseconds(x::Microseconds)  = Milliseconds( milliseconds(x) )
Milliseconds(x::Milliseconds)  = x
Milliseconds(x::Seconds)       = Milliseconds( milliseconds(x) )
Milliseconds(x::Minutes)       = Milliseconds( milliseconds(x) )
Milliseconds(x::Hours)         = Milliseconds( milliseconds(x) )
Milliseconds(x::Days)          = Milliseconds( milliseconds(x) )
Milliseconds(x::Weeks)         = Milliseconds( milliseconds(x) )

Seconds(x::Nanoseconds)   = Seconds( seconds(x) )
Seconds(x::Microseconds)  = Seconds( seconds(x) )
Seconds(x::Milliseconds)  = Seconds( seconds(x) )
Seconds(x::Seconds)       = x
Seconds(x::Minutes)       = Seconds( seconds(x) )
Seconds(x::Hours)         = Seconds( seconds(x) )
Seconds(x::Days)          = Seconds( seconds(x) )
Seconds(x::Weeks)         = Seconds( seconds(x) )



for (T,M,B,S) in ( 
   (:Nanoseconds, :NANOSECONDS_PER_NANOSECOND, :NanosBased, :nanoseconds),
   (:Microseconds, :NANOSECONDS_PER_MICROSECOND, :NanosBased, :microseconds),
   (:Milliseconds, :NANOSECONDS_PER_MILLISECOND, :NanosBased, :milliseconds),
   (:Seconds, :NANOSECONDS_PER_SECOND, :NanosBased, :seconds),
   (:Minutes, :NANOSECONDS_PER_MINUTE, :NanosBased, :minutes),
   (:Hours, :NANOSECONDS_PER_MINUTE, :NanosBased, :hours),
   (:Days,  :NANOSECONDS_PER_DAY, :NanosBased, :days),
   (:Weeks, :NANOSECONDS_PER_WEEK, :NanosBased, :weeks) )
  @eval begin
    struct $T <: $B
        value::Int4Span
    end
    @inline value(x::$T) = x.value
    Base.convert(::Type{$T}, x::$T) = x

    struct $S <: $B end
    $S(x::$T) = value(x)
    Base.:(*)(x::Int4Span, s::Type{$S}) = $T(x)
       
    nanoseconds(x::$T) = value(x) * M
    microseconds(x::$T) = value(x) * div(M, NANOSECONDS_PER_MICROSECOND)
    milliseconds(x::$T) = value(x) * div(M, NANOSECONDS_PER_MILLISECOND)
    seconds(x::$T) = value(x) * div(M, NANOSECONDS_PER_SECOND)
    minutes(x::$T) = value(x) * div(M, NANOSECONDS_PER_MINUTE)
    hours(x::$T) = value(x) * div(M, NANOSECONDS_PER_HOUR)
    days(x::$T) = value(x) * div(M, NANOSECONDS_PER_DAY)
    weeks(x::$T) = value(x) * div(M, NANOSECONDS_PER_WEEK) 
  end
end

for (T,M,B,S) in ( (:Nanoseconds, :NANOSECONDS_PER_NANOSECOND, :NanosBased, :nanoseconds),
                 (:Microseconds, :NANOSECONDS_PER_MICROSECOND, :NanosBased, :microseconds),
                 (:Milliseconds, :NANOSECONDS_PER_MILLISECOND, :NanosBased, :milliseconds),
                 (:Seconds, :NANOSECONDS_PER_SECOND, :NanosBased, :seconds),
                 (:Minutes, :NANOSECONDS_PER_MINUTE, :NanosBased, :minutes),
                 (:Hours, :NANOSECONDS_PER_MINUTE, :NanosBased, :hours),
                 (:Days,  :NANOSECONDS_PER_DAY, :NanosBased, :days),
                 (:Weeks, :NANOSECONDS_PER_WEEK, :NanosBased, :weeks) )
  @eval begin
    Base.convert(Type{Nanoseconds},  x::$T) = Nanoseconds(M * value(x))
    Base.convert(Type{Microseconds}, x::$T) = Microseconds(div(M * value(x), NANOSECONDS_PER_MICROSECOND))
    Base.convert(Type{Milliseconds}, x::$T) = Milliseconds(div(M * value(x), NANOSECONDS_PER_MILLISECOND))
    Base.convert(Type{Seconds}, x::$T) = Seconds(div(M * value(x), NANOSECONDS_PER_SECOND))
    Base.convert(Type{Minutes}, x::$T) = Minutes(div(M * value(x), NANOSECONDS_PER_MINUTE))
    Base.convert(Type{Hours}, x::$T) = Hours(div(M * value(x), NANOSECONDS_PER_HOUR))
    Base.convert(Type{Days}, x::$T) = Days(div(M * value(x), NANOSECONDS_PER_DAY))
    Base.convert(Type{Weeks}, x::$T) = Weeks(div(M * value(x), NANOSECONDS_PER_WEEK))
       
    $S(x::Nanoseconds) = value($T(x))
    $S(x::Microseconds) = value($T(x))
    $S(x::Milliseconds) = value($T(x))
    $S(x::Seconds) = value($T(x))
    $S(x::Minutes) = value($T(x))
    $S(x::Hours) = value($T(x))
    $S(x::Days) = value($T(x))
    $S(x::Weeks) = value($T(x))

  end
end

for (T,M,B) in ( (:Months, :MONTHS_PER_MONTH, :MonthBased),
                 (:QuarterYears, :MONTHS_PER_QUARTERYEAR, :MonthBased),
                 (:Years, :MONTHS_PER_YEAR, :MonthBased),
  @eval begin
    struct $T <: $B
        value::Int4Span
    end
    @inline value(x::$T) = x.value    
    Base.convert(Type{Months},  x::$T) = Months(M * value(x))
    Base.convert(Type{QuarterYears},  x::$T) = QuarterYears(div(M * value(x), MONTHS_PER_QUARTERYEAR))
    Base.convert(Type{Years}, x::$T) = Years(div(M * value(x), MONTHS_PER_YEAR))
  end
end

for (T,M,B) in ( (:Nanosecond, :NANOSECONDS_PER_NANOSECOND, :NanosBased),
                 (:Microsecond, :NANOSECONDS_PER_MICROSECOND, :NanosBased),
                 (:Millisecond, :NANOSECONDS_PER_MILLISECOND, :NanosBased),
                 (:Second, :NANOSECONDS_PER_SECOND, :NanosBased),
                 (:Minute, :NANOSECONDS_PER_MINUTE, :NanosBased),
                 (:Hour, :NANOSECONDS_PER_MINUTE, :NanosBased),
                 (:Day,  :NANOSECONDS_PER_DAY, :NanosBased),
                 (:Week, :NANOSECONDS_PER_WEEK, :NanosBased) )
  @eval begin

    struct $T <: $B
        value::Int4Span
    end
    @inline value(x::$T) = x.value     
    Base.convert(Type{Nanosecond},  x::$T) = Nanosecond( rem(M * value(x), NANOSECONDS_PER_MICROSECOND) )
    Base.convert(Type{Microsecond}, x::$T) = Microsecond( rem(value(Microseconds(x)), MICROSECONDS_PER_MILLISECOND) )
    Base.convert(Type{Millisecond}, x::$T) = Millisecond( rem(value(Milliseconds(s)), MILLISECONDS_PER_SECOND) )
    Base.convert(Type{Second}, x::$T) = Second( rem(value(Seconds(s)), SECONDS_PER_MINUTE) )
    Base.convert(Type{Minute}, x::$T) = Minute( rem(value(Minutes(s)), MINUTES_PER_HOUR) )
    Base.convert(Type{Hour}, x::$T) = Hour( rem(value(Hours(s)), HOURS_PER_DAY) )
    Base.convert(Type{Day}, x::$T) = Day( div(value(Hours(s)), HOURS_PER_DAY) )
    Base.convert(Type{Week}, x::$T) = Week( div(value(Hours(s)), HOURS_PER_WEEK) )
  end
end

for (T,M,B) in ( (:Month, :MONTHS_PER_MONTH, :MonthBased),
                 (:QuarterYear, :MONTHS_PER_QUARTERYEAR, :MonthBased),    
                 (:Year, :MONTHS_PER_YEAR, :MonthBased),
  @eval begin

    struct $T <: $B
        value::Int4Span
    end
    @inline value(x::$T) = x.value  
    Base.convert(Type{Month},  x::$T) = Month( rem(M * value(x), MONTHS_PER_YEAR) )
    Base.convert(Type{QuarterYear}, x::$T) = QuarterYear(  div(M * value(x), MONTHS_PER_QUARTERYEAR) )
    Base.convert(Type{Year}, x::$T) = Year(  div(M * value(x), MONTHS_PER_YEAR) ) 
  end
end


end # module
